package com.senriot.quickup.web.rest

import com.querydsl.core.types.Predicate
import com.senriot.cloud.common.AbstractController
import com.senriot.cloud.common.annotation.EntityName
import com.senriot.cloud.common.error.BadRequestAlertException
import com.senriot.cloud.common.error.EmailAlreadyUsedException
import com.senriot.cloud.common.error.LoginAlreadyUsedException
import com.senriot.cloud.common.result.R
import com.senriot.cloud.common.security.ADMIN
import com.senriot.quickup.domain.User
import com.senriot.quickup.repository.UserRepository
import com.senriot.quickup.service.MailService
import com.senriot.quickup.service.UserService
import com.senriot.quickup.service.dto.StaffDTO
import com.senriot.quickup.service.dto.UserDTO
import com.senriot.quickup.web.rest.vm.RestPasswordVM
import io.github.jhipster.web.util.HeaderUtil
import io.github.jhipster.web.util.ResponseUtil
import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.querydsl.binding.QuerydslPredicate
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import org.springframework.web.servlet.ModelAndView
import java.net.URI
import javax.validation.Valid

/**
 * REST controller for managing users.
 *
 * This class accesses the [User] entity, and needs to fetch its collection of authorities.
 *
 * For a normal use-case, it would be better to have an eager relationship between User and Authority,
 * and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join
 * which would be good for performance.
 *
 * We use a View Model and a DTO for 3 reasons:
 *
 * * We want to keep a lazy association between the user and the authorities, because people will
 * quite often do relationships with the user, and we don't want them to get the authorities all
 * the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users'
 * application because of this use-case.
 * *  Not having an outer join causes n+1 requests to the database. This is not a real issue as
 * we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests,
 * but then all authorities come from the cache, so in fact it's much better than doing an outer join
 * (which will get lots of data from the database, for each HTTP call).
 * *  As this manages users, for security reasons, we'd rather have a DTO layer.
 *
 * Another option would be to have a specific JPA entity graph to handle this case.
 */
@RestController
@RequestMapping("/api/users")
@EntityName("user")
@Api(tags = ["用户"])
class UserResource(private val mailService: MailService, private val userRepository: UserRepository) :
        AbstractController<User, Long, UserDTO, UserService>()
{
    override fun loadAll(sort: Sort, @QuerydslPredicate(root = User::class) predicate: Predicate?): List<UserDTO>
    {
        return super.loadAll(sort, predicate)
    }

    override fun loadPage(page: Pageable, @QuerydslPredicate(root = User::class) predicate: Predicate?): Page<UserDTO>
    {
        return super.loadPage(page, predicate)
    }

    override fun count(@QuerydslPredicate(root = User::class) predicate: Predicate?): ResponseEntity<Long>
    {
        return super.count(predicate)
    }

    override fun exportXls(
            @QuerydslPredicate(root = User::class)
            predicate: Predicate?
    ): ModelAndView
    {
        return super.exportXls(predicate)
    }

    @PreAuthorize("hasRole(\"$ADMIN\")")
    override fun create(@Valid @RequestBody dto: UserDTO): ResponseEntity<UserDTO>
    {
        log.debug("创建用户 : {}", dto)

        when
        {
            dto.id != null                                                     -> throw BadRequestAlertException(
                    "A new user cannot already have an ID",
                    "userManagement",
                    "idexists"
            )
            // Lowercase the user login before comparing with database
            userRepository.findOneByLogin(dto.login!!.toLowerCase()).isPresent -> throw LoginAlreadyUsedException()
            userRepository.findOneByEmailIgnoreCase(dto.email).isPresent       -> throw EmailAlreadyUsedException()
            else                                                               ->
            {
                val newUser = service.createUser(dto)
                mailService.sendCreationEmail(newUser)
                return ResponseEntity.created(URI("/api/users/" + newUser.login))
                        .headers(HeaderUtil.createAlert(applicationName, "userManagement.created", newUser.login))
                        .body(service.mapper.toDto(newUser))
            }
        }
    }

    @PreAuthorize("hasRole(\"$ADMIN\")")
    override fun update(@RequestBody dto: UserDTO): ResponseEntity<UserDTO>
    {
        log.debug("更新用户 : {}", dto)
        val existingUser = userRepository.findOneByLogin(dto.login!!.toLowerCase())
        if (existingUser.isPresent && existingUser.get().id != dto.id)
        {
            throw LoginAlreadyUsedException()
        }
        val updatedUser = service.updateUser(dto)

        return ResponseUtil.wrapOrNotFound(
                updatedUser,
                HeaderUtil.createAlert(applicationName, "userManagement.updated", dto.login)
        )
    }

    /**
     * 更新状态
     * @param status Int
     * @return ResponseEntity<UserDTO>
     */
    @PutMapping("{id}/status")
    @ApiOperation("更新状态")
    fun updateStatus(@PathVariable id: Long, status: Int): ResponseEntity<StaffDTO>?
    {
        val user = service.updateStatus(id, status)

        return ResponseUtil.wrapOrNotFound(
                user,
                HeaderUtil.createAlert(applicationName, "userManagement.updated", id.toString())
        )
    }

    /**
     * 获取人员
     * @param deviceId String? 根据设备Id获取，只获取未参数此设备分成的人员
     * @param page Pageable
     * @param predicate Predicate?
     * @return Page<StaffDTO>
     */
    @GetMapping("staffs")
    @ApiOperation("获取人员")
    fun getStaffs(
            @RequestParam(required = false) deviceId: String?,
            page: Pageable,
            @QuerydslPredicate(root = User::class)
            predicate: Predicate?): Page<StaffDTO> =
            service.getStaffs(page, predicate, deviceId)


    /**
     * 获取申请成为人员的链接
     * @return String
     */
    @GetMapping("requestLink")
    @ApiOperation("获取申请成为人员的链接")
    fun requestLink(): R<String>
    {
        return service.requestLink()
    }


    @PostMapping("rest-password")
    @ApiOperation("修改密码")
    fun restPassword(@RequestBody vm: RestPasswordVM) = service.restPassword(vm.oldPassword, vm.newPassword)
}
